prufer序列

prufer序列是一种无根树的序列表示方法,一个节点个数为 $n$ 的无根树对应着唯一一种长度为 $n - 2$ 的prufer序列

无根树化prufer序列

每次取编号最小的叶子节点,将其指向的“父亲”(就是它唯一指向的)节点加入prufer序列中,最终剩余两个连接的节点结束

prufer序列化无根树

对于prufer序列 $A= \{a_1, a_2, a_3, …\}$,每次取最前面的元素 $a_s$,在总点集中找到最小的没有在 $A$ 中的节点 $t$,将 $a_s, t$ 连边并删去 $a_s$,可以使用 $set$ 等维护

相关定理

  • $n$ 个点的有标号完全图无根树计数:$n^{n - 2}$
  • $n$ 个点,每个点的度数为 $\{d_1, d_2, d_3, …\}$ 的有标号无根树计数:$\frac{(n - 2)!}{d_1!d_2!d_3!…}$(实际上就是一个多重集的排列数)
  • $n$ 个点的prufer序列中第 $i$ 个点恰好出现 $d_i - 1$ 次
  • $n$ 个点度数为 $\{d_1, d_2, …, d_k\}$,剩余 $k + 1 \thicksim n$ 未知度数的节点的有标号无根树计数:设剩余的位置 $d_l = (n - 2) - \sum\limits_{i = 1}^k (d_i- 1) $,先将它们看作一个整体再局部求解得到 $ans = \frac{(n - 2)!}{d_1!d_2!…d_k!d_l!}(n - k)^{d_l}$
  • $n$ 个点的有标号有根树计数:$n^{n - 1}$
  • $n$ 个点的无标号有根树计数:待续
  • $n$ 个点的无标号无根树计数:待续

本题题解

题目描述

有 $n$ 个有标号点,第 $i$ 个点的度数上限为 $A_i$

现在对于每个 $s (1 \le s \le n)$,问从这 $n$ 个点中选出一些点组成大小为 $s$ 的有标号无根树的方案数

数据范围

对于 $100\%$ 的数据,$n \le 100$

考虑直接将无根树转化为prufer序列,由唯一性在序列上直接 $dp$
令 $f_{i, j, k}$ 表示前 $i$ 个点选 $j$ 个组成长度为 $k$ 的prufer序列的方案数

显然
$$
\begin{aligned}
f_{i + 1, j, k} &+= f_{i, j, k} \\
f_{i + 1, j + 1, k + l(l \in [0, A_{i + 1} - 1])} &+= f_{i, j, k} \times \dbinom{k + l}{l}
\end{aligned}
$$

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <iostream>
#include <cstdio>
#include <cstring>

#define MOD 1004535809

using namespace std;

typedef long long LL;

const int MAXN = 100 + 10;

LL C[MAXN][MAXN]= {0};
LL f[MAXN][MAXN][MAXN]= {0};

int N;
int limit[MAXN];

int getnum () {
int num = 0;
char ch = getchar ();

while (! isdigit (ch))
ch = getchar ();
while (isdigit (ch))
num = (num << 3) + (num << 1) + ch - '0', ch = getchar ();

return num;
}
void write (LL x) {
if (x >= 10)
write (x / 10);
putchar (x % 10 + '0');
}

int main () {
freopen ("tree.in", "r", stdin);
freopen ("tree.out", "w", stdout);

N = getnum ();
for (int i = 1; i <= N; i ++)
limit[i] = getnum ();
C[0][0] = 1;
for (int i = 1; i <= N; i ++) {
C[i][0] = 1;
for (int j = 1; j <= N; j ++)
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD;
}
f[0][0][0] = 1;
for (int i = 1; i <= N; i ++)
for (int j = 0; j <= i; j ++)
for (int k = 0; k <= N; k ++) { // 注意需眼神到N
f[i][j][k] = f[i - 1][j][k];
if (j > 0) {
for (int l = 0; l <= k && l < limit[i]; l ++) // 注意因为可以作叶子所以l需要从0开始枚举
f[i][j][k] = (f[i][j][k] + f[i - 1][j - 1][k - l] * C[k][l] % MOD) % MOD;
}
}
write (N);
for (int i = 2; i <= N; i ++)
putchar (' '), write (f[N][i][i - 2]);
puts ("");

return 0;
}

/*
3
2 2 1
*/

/*
6
1 5 2 4 4 4

output:
6 15 50 160 392 509
*/